package org.qpcr.community.project.web;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.reactor.excel.ReactorExcel;
import org.hswebframework.web.api.crud.entity.QueryNoPagingOperation;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.authorization.annotation.Resource;
import org.hswebframework.web.authorization.annotation.SaveAction;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
import org.hswebframework.web.exception.ValidationException;
import org.hswebframework.web.i18n.LocaleUtils;
import org.qpcr.community.io.excel.AbstractImporter;
import org.qpcr.community.io.file.FileManager;
import org.qpcr.community.project.model.entity.TestSequenceInstanceEntity;
import org.qpcr.community.project.service.LocalTestSequenceInstanceService;
import org.qpcr.community.project.web.excel.ImportResult;
import org.qpcr.community.project.web.excel.TestSequenceExcelFilterColumns;
import org.qpcr.community.project.web.excel.TestSequenceExcelImporter;
import org.qpcr.community.project.web.excel.TestSequenceExcelInfo;
import org.qpcr.community.web.response.ValidationResult;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.concurrent.Queues;
import reactor.util.function.Tuples;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;

/**
 * 序列管理
 */
@RestController
@RequestMapping( {"/project/test-sequence", "/project/test/sequence"})
@Authorize
@Resource(id = "test-sequence-instance", name = "检测序列")
@Slf4j
@Tag(name = "检测序列接口")
public class TestSequenceController implements
    ReactiveServiceCrudController<TestSequenceInstanceEntity, String> {
    @Getter
    private final LocalTestSequenceInstanceService service;
    private final TestSequenceExcelFilterColumns filterColumns;
    private final FileManager fileManager;
    private final WebClient webClient;
    private final TransactionalOperator transactionalOperator;

    @SuppressWarnings("all")
    public TestSequenceController(LocalTestSequenceInstanceService service,
                              TestSequenceExcelFilterColumns filterColumns,
                              FileManager fileManager,
                              WebClient.Builder builder,
                              TransactionalOperator transactionalOperator) {
        this.service = service;
        this.filterColumns = filterColumns;
        this.fileManager = fileManager;
        this.webClient = builder.build();
        this.transactionalOperator = transactionalOperator;
    }
    DataBufferFactory bufferFactory = new DefaultDataBufferFactory();

    @GetMapping("/template.{format}")
    @QueryAction
    @Operation(summary = "下载检测序列导入模版")
    public Mono<Void> downloadExportTemplate(ServerHttpResponse response,
                                             @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("检测序列导入模版." + format, StandardCharsets.UTF_8
                        .displayName())));
        return
                Mono.just(new TestSequenceInstanceEntity())
                        .map(tp1 -> TestSequenceExcelInfo.getTemplateHeaderMapping(filterColumns))
                        .defaultIfEmpty(TestSequenceExcelInfo.getTemplateHeaderMapping(filterColumns))
                        .flatMapMany(headers ->
                                ReactorExcel.<TestSequenceExcelInfo>writer(format)
                                        .headers(headers)
                                        .writeBuffer(Flux.empty()))
                        .doOnError(err -> log.error(err.getMessage(), err))
                        .map(bufferFactory::wrap)
                        .as(response::writeWith);
    }

    /**
     * 导出检测序列数据
     *
     * @param response  response
     * @param parameter parameter
     * @param format    format
     * @return Mono<Void>
     * @throws IOException IOException
     */
    @PostMapping("/export.{format}")
    @QueryAction
    @QueryNoPagingOperation(summary = "导出检测序列数据")
    public Mono<Void> export(ServerHttpResponse response,
                             @RequestBody QueryParamEntity parameter,
                             @PathVariable @Parameter(description = "文件格式,支持csv,xlsx") String format) throws IOException {
        response.getHeaders().set(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=".concat(URLEncoder.encode("检测序列." + format, StandardCharsets.UTF_8
                        .displayName())));
        parameter.setPaging(false);
        return Mono.just(
                        //检测序列
                        service.query(parameter)
                )
                .map(tp1 -> Tuples
                        .of(
                                //表头
                                TestSequenceExcelInfo.getExportHeaderMapping(filterColumns),
                                Collections.emptyList()
                        ))
                .defaultIfEmpty(Tuples.of(TestSequenceExcelInfo.getExportHeaderMapping(filterColumns),
                        Collections.emptyList()))
                .flatMapMany(headerAndConfigKey -> ReactorExcel
                        .<TestSequenceExcelInfo>writer(format)
                        .headers(headerAndConfigKey.getT1())
                        .converter(TestSequenceExcelInfo::toMap)
                        .writeBuffer(service
                                        .query(parameter)
                                        .flatMap(entity -> Mono.just(FastBeanCopier.copy(entity, new TestSequenceExcelInfo())))
                                        .buffer(200)
                                        .flatMap(Flux::fromIterable)
                                , 512 * 1024))//缓冲512k
                .doOnError(err -> log.error(err.getMessage(), err))
                .map(bufferFactory::wrap)
                .as(response::writeWith);
    }

    @GetMapping(value = "/import/_withlog", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @SaveAction
    @Operation(summary = "导入检测序列数据，并提供日志下载")
    public Flux<ImportResult> doBatchImportWithLog(
            @RequestParam @Parameter(description = "文件地址,支持csv,xlsx文件格式") String fileUrl,
            @RequestParam(defaultValue = "32") @Parameter int speed
    ) {
        return Authentication
                .currentReactive()
                .flatMapMany(auth -> Mono.zip(
                                        //检测序列
                                        service.createQuery().fetch().collectList(),
                                        Mono.just(new TestSequenceInstanceEntity())
                                )
                                .map(tp2 -> new TestSequenceExcelImporter(fileManager, webClient, tp2.getT1(), auth))
                                .flatMapMany(importer -> importer
                                        .doImport(fileUrl)
                                        .groupBy(
                                                result -> result.isSuccess() && result.getType() == AbstractImporter.ImportResultType.data,
                                                Integer.MAX_VALUE
                                        )
                                        .flatMap(group -> {
                                            // 处理导入成功的检测序列
                                            if (group.key()) {
                                                return group
                                                        .map(result -> result.getData().getTestSequenceInstance())
                                                        .as(flux -> handleImportDevice(flux, speed));
                                            }
                                            // 返回错误信息和导入结果详情文件地址
                                            return group
                                                    .map(result -> {
                                                        ImportResult response = new ImportResult();
                                                        response.setSuccess(result.isSuccess());
                                                        if (StringUtils.hasText(result.getMessage())) {
                                                            response.setMessage(String.format("第%d行：%s", result.getRow(), result.getMessage()));
                                                        }
                                                        response.setDetailFile(result.getDetailFile());
                                                        return response;
                                                    });
                                        })
                                )
                );
    }
    private Flux<ImportResult> handleImportDevice(Flux<TestSequenceInstanceEntity> flux,
                                                  int speed) {
        return flux
                .buffer(100)//每100条数据保存一次
                .map(Flux::fromIterable)
                .flatMap(buffer -> Mono
                                .zip(buffer
                                    .as(service::save)
                                    .flatMap(Mono::just),
                                    Mono.just(SaveResult.of(0, 0))
                                )
                                .as(transactionalOperator::transactional),
                        Math.min(speed, Queues.XS_BUFFER_SIZE)
                )
                .map(res -> ImportResult.success(res.getT1()))
                .onErrorResume(err -> Mono.just(ImportResult.error(err)));
    }

    @PostMapping("/seqcode/_validate")
    @Authorize(merge = false)
    @Operation(summary = "序列名验证")
    public Mono<ValidationResult> seqcodeValidate(@RequestBody(required = true)  TestSequenceInstanceEntity seq) {

        if(seq.getId()!=null && seq.getId().isEmpty()){
            return LocaleUtils
                .currentReactive()
                .flatMap(locale -> {
                    return service.findBySeqCode(seq.getCode())
                                  .map(i -> ValidationResult.error(LocaleUtils.resolveMessage(
                                      "error.seqcode_already_exist",
                                      locale,
                                      "序列编号" + seq.getCode() + "已存在", seq.getCode()))
                                  );
                })
                .defaultIfEmpty(ValidationResult.success())
                .onErrorResume(ValidationException.class,
                               e -> e.getLocalizedMessageReactive().map(ValidationResult::error));
        }else{
            return LocaleUtils
                .currentReactive()
                .flatMap(locale -> {
                    return service.findBySeqCode(seq.getCode(),seq.getId())
                                  .map(i -> ValidationResult.error(LocaleUtils.resolveMessage(
                                      "error.seqcode_already_exist",
                                      locale,
                                      "序列编号" + seq.getCode() + "已存在", seq.getCode()))
                                  );
                })
                .defaultIfEmpty(ValidationResult.success())
                .onErrorResume(ValidationException.class,
                               e -> e.getLocalizedMessageReactive().map(ValidationResult::error));
        }

    }

    /**
     * 查询温度校准序列
     */
    @GetMapping("/temperature-calibration-sequence")
    public Mono<TestSequenceInstanceEntity> getTemperatureCalibrationSequence(){
        return service.findBySeqCode("C0");
    }
}
